﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
using ConfigurationSettings;

namespace CustomControls
{

    public sealed class ProgressBarEx : Control
    {

        public event EventHandler<EventArgs> ValueUpdated;

        #region ** Member Variables **

        readonly StringFormat _textFormat;
        long _value;
        long _step;
        long _maximum;
        long _minimum;
        decimal _percentPaint;
        int progressAmount = 0;
        int oldProgressAmount = -1;
        readonly Color _borderColor = Color.Black;
        int _borderSize = 1;
        Color _foreColor2;
        FillDirectionType _fillDirection;
        ProgressDirectionType _progressDirection;
        Rectangle _displayRectangle;
        Rectangle _progressRectangle;

        LinearGradientBrush _foreBrush;
        SolidBrush _textBrush;
        LinearGradientBrush _shadowBrush;

        Color _textColor;
        BarType _progressBarType;
        readonly Blend _shadowBlend;
        readonly Color _shadowDark;
        readonly Color _shadowLight;

        private readonly TaskScheduler _uiScheduler;
        private static Task _uiTask;
        private object _uiLock;

        #endregion

        #region ** Public Properties **

        public BarType ProgressBarType
        {
            get { return _progressBarType; }
            set
            {
                _progressBarType = value;
                _value -= _step;
                PerformStep();
            }
        }

        public Color TextColor
        {
            get { return _textColor; }
            set
            {
                _textColor = value;
                _value -= _step;

                PerformStep();
            }
        }

        public override Rectangle DisplayRectangle
        {
            get { return _displayRectangle; }
        }

        public ProgressDirectionType ProgressDirection
        {
            get { return _progressDirection; }
            set
            {
                _progressDirection = value;
                _value -= _step;
                PerformStep();
            }
        }

        public FillDirectionType FillDirection
        {
            get { return _fillDirection; }
            set
            {
                _fillDirection = value;
                OnClientSizeChanged(null);
                _value -= _step;

                PerformStep();
            }
        }

        public Color ForeColor2
        {
            get { return _foreColor2; }
            set
            {
                _foreColor2 = value;
                _value -= _step;

                PerformStep();
            }
        }

        public long Maximum
        {
            get { return _maximum; }
            set
            {
                if (value < _minimum)
                    _minimum = value - 1;

                _maximum = value;
                _value -= _step;

                PerformStep();
            }
        }

        public long Minimum
        {
            get { return _minimum; }
            set
            {
                if (value > _maximum)
                    _maximum = value + 1;

                _minimum = value;
                _value -= _step;

                PerformStep();
            }
        }

        public long Value
        {
            get
            {
                return _value;
            }

            set
            {
                {
                    //Console.WriteLine("ProgressBarEx.Value Thread: " + Thread.CurrentThread.ManagedThreadId.ToString());
                    _value = value - _step; //Take step off because PerformStep adds _step back in.
                    PerformStep();
                }
            }
        }

        public long Step
        {
            get
            {
                return _step;
            }

            set
            {
                _step = value;
            }
        }

        #endregion

        #region ** Constructor **

        public ProgressBarEx()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.ResizeRedraw, true);
            SetStyle(ControlStyles.UserPaint, true);

            _shadowBlend = new Blend(5);
            _shadowBlend.Positions[0] = 0;
            _shadowBlend.Factors[0] = 0;

            _shadowBlend.Positions[1] = .30F;
            _shadowBlend.Factors[1] = 1.0F;

            _shadowBlend.Positions[2] = 0.31F;
            _shadowBlend.Factors[2] = 0;

            _shadowBlend.Positions[3] = 0.32F;
            _shadowBlend.Factors[3] = 1.0F;

            _shadowBlend.Positions[4] = 1.0F;
            _shadowBlend.Factors[4] = 0;

            _shadowDark = Color.FromArgb(80, Color.DarkGray);
            _shadowLight = Color.FromArgb(80, Color.White);

            try
            {
                // ReSharper disable UseObjectOrCollectionInitializer
                _textFormat = new StringFormat();
                // ReSharper restore UseObjectOrCollectionInitializer

                _textFormat.Alignment = StringAlignment.Center;
                _textFormat.LineAlignment = StringAlignment.Center;
                _textFormat.FormatFlags = StringFormatFlags.NoWrap;
                _textFormat.Trimming = StringTrimming.None;

            }
            catch
            {
                if (_textFormat != null)
                    _textFormat.Dispose();

                throw;
            }

            _value = 50;
            _step = 1;
            _maximum = 100;
            _minimum = 0;
            BackColor = Color.White;
            ForeColor = Color.GreenYellow;
            _foreColor2 = Color.White;
            _textColor = Color.Black;
            _progressDirection = ProgressDirectionType.GoRight;
            _fillDirection = FillDirectionType.Vertical;
            _progressBarType = BarType.PercentageType;

            _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            _uiLock = new object();
        }

        #endregion

        protected override void Dispose(bool argDisposing)
        {
            _textFormat.Dispose();
            _foreBrush.Dispose();
            _textBrush.Dispose();
            _shadowBrush.Dispose();

            base.Dispose(argDisposing);
        }

        protected override void OnPaint(PaintEventArgs argPntEventArgs)
        {
            {
                string displayString;
                Pen borderPen;
                Rectangle textRectangle = Rectangle.Empty;

                //TODO: 003 - Move all the math and logic to a background thread to free up the Paint event.
                if (progressAmount == oldProgressAmount)
                { return; }

                //Clear the background
                using (SolidBrush backBrush = new SolidBrush(BackColor))
                {
                    argPntEventArgs.Graphics.FillRectangle(backBrush, ClientRectangle);
                }

                if (_progressBarType == BarType.PercentageType)
                {
                    displayString = _percentPaint.ToString("P");
                }
                else
                {
                    displayString = _value.ToString();
                }

                //Determine how long the control is.
                switch (_progressDirection)
                {
                    case ProgressDirectionType.GoRight:
                        {
                            _progressRectangle.Location = new Point(progressAmount - _displayRectangle.Width, 0);
                            textRectangle = new Rectangle(_borderSize, _borderSize, progressAmount, _displayRectangle.Height);

                            break;
                        }
                    case ProgressDirectionType.GoLeft:
                        {
                            _progressRectangle.Location = new Point(_displayRectangle.Width - progressAmount, 0);
                            textRectangle = new Rectangle(_progressRectangle.X, _borderSize, progressAmount, _displayRectangle.Height);

                            break;
                        }
                    case ProgressDirectionType.GoUp:
                        {
                            _progressRectangle.Location = new Point(0, _displayRectangle.Height - progressAmount);
                            textRectangle = new Rectangle(_borderSize, _progressRectangle.Y, _displayRectangle.Width, progressAmount);

                            break;
                        }
                    case ProgressDirectionType.GoDown:
                        {
                            _progressRectangle.Location = new Point(0, progressAmount - _displayRectangle.Height);
                            textRectangle = new Rectangle(_borderSize, _borderSize, _displayRectangle.Width, progressAmount);

                            break;
                        }
                }

                //paint the percentage rectangle
                argPntEventArgs.Graphics.FillRectangle(_foreBrush, _progressRectangle);

                SizeF textSize = argPntEventArgs.Graphics.MeasureString(displayString, Font);

                //Paint the percentage text if there is room
                if (textRectangle.Width > textSize.Width)
                {
                    argPntEventArgs.Graphics.DrawString(displayString, Font, _textBrush, textRectangle, _textFormat);
                }

                //Set Offeset to half the pen width
                int xOffset = (int)Math.Truncate((_borderSize - ClientRectangle.X) / 2M);
                int yOffset = (int)Math.Truncate((_borderSize - ClientRectangle.Y) / 2M);

                //Draw shadow rectangle to appear raised            
                argPntEventArgs.Graphics.FillRectangle(_shadowBrush, ClientRectangle);

                //Finally draw the border last to cover things behind the border
                using (borderPen = new Pen(_borderColor, _borderSize))
                {
                    borderPen.Alignment = PenAlignment.Center;

                    argPntEventArgs.Graphics.DrawRectangle(borderPen, xOffset, yOffset, ClientRectangle.Width - borderPen.Width, ClientRectangle.Height - borderPen.Width);
                }

                argPntEventArgs.Graphics.Flush();

                ValueUpdated.Raise(this, new EventArgs());
            }
        }

        protected override void OnClientSizeChanged(EventArgs argEvtArgs)
        {
            //Set DisplayRectangle
            base.OnClientSizeChanged(argEvtArgs);

            _displayRectangle = new Rectangle(_borderSize, _borderSize,
                ClientRectangle.Width - (_borderSize * 2), ClientRectangle.Height - (_borderSize * 2));

            //Set PercentRectangle based on the new DisplayRectangle.
            _progressRectangle = new Rectangle(new Point(0, 0), _displayRectangle.Size);

            if (_fillDirection == FillDirectionType.Horizontal)
            {
                _foreBrush = new LinearGradientBrush(_progressRectangle, _foreColor2, ForeColor, 0.0F);
            }
            else
            {
                _foreBrush = new LinearGradientBrush(_progressRectangle, ForeColor, _foreColor2, 90.0F);
            }

            Point localOrigin = new Point(0, 0);
            Point localOffset = new Point(0, _displayRectangle.Height);
            try
            {
                _shadowBrush = new LinearGradientBrush(localOrigin, localOffset, _shadowLight, _shadowDark) { Blend = _shadowBlend };

                _textBrush = new SolidBrush(_textColor);
            }
            catch
            {
                if (_shadowBrush != null)
                {
                    _shadowBrush.Dispose();
                }
                throw;
            }
        }

        public void PerformStep()
        {
            {
                _value += _step;

                //Determine what percentage of the control needs to be painted.
                _percentPaint = _value / (decimal)_maximum;
                progressAmount = (int)Math.Truncate(_displayRectangle.Width * _percentPaint);

                if (IsHandleCreated)
                {

                    if (_value < _maximum)
                    {

                        //I do this to keep from waiting for the UI thread.
                        //If there is no update task it will create one
                        //TODO: 000 - Change this to use !Monitor.TryEnter(_timerLock) and Monitor.Exit(_timerLock);
                        //if (_uiTask == null)
                        //{
                        //    _log.Debug("_uiTask == null");

                        //    _uiTask = Task.Factory.StartNew(() =>
                        //        {
                                    Invalidate();

                                    //Refresh();
                        //        },
                        //        CancellationToken.None,
                        //        TaskCreationOptions.None,
                        //        _uiScheduler);
                        //}
                        //else
                        //{
                        //    _log.Debug("uiTask != null");

                        //    if (_uiTask.IsCompleted)
                        //    {
                        //        _log.Debug("uiTask is Complete");

                        //        _uiTask = Task.Factory.StartNew(() =>
                        //        {
                        //            Invalidate();

                        //            Refresh();
                        //        },
                        //        CancellationToken.None,
                        //        TaskCreationOptions.None,
                        //        _uiScheduler);
                        //    }
                        //    else
                        //    {
                        //        _log.Debug("uiTask is not Complete");
                        //    }
                        //}

                    }
                }
            }
        }

    }

    #region ** Enum Types **

    public enum ProgressDirectionType
    {
        GoRight,
        GoUp,
        GoLeft,
        GoDown
    }
    public enum FillDirectionType
    {
        Horizontal,
        Vertical
    }
    public enum BarType
    {
        ProgressType,
        PercentageType //,
        //Marquee
    }

    #endregion

}
